#ifndef ARRAY_COPY_H
#define ARRAY_COPY_H

#include <cstring>
#include <iterator>

/**
 *  @brief  A wrapper iterator that wrape a constant value to behave like an iterator
 *  @tparam T the type of constant value
 *
 *  iterator_on_one_element wraps a constant value type of T as an normal iterator.
 *  It provides all iterator interfaces and just acts like a random access iterator. 
 *  However you traverse this iterator, it always iterates on this constant. So the 
 *  client side will not be aware of this constant. This special version of iterator
 *  is used for the binary operations between an array type and a constant value.
 *  Applying such operations will be same as if applying operations between two array
 *  types--traversing each right/left elemet currently by using iterators but with 
 *  a right constant value.
 *  
 */
template <typename T>
struct iterator_on_one_element
{
	typedef std::random_access_iterator_tag iterator_category;
	typedef std::ptrdiff_t difference_type;
	typedef T  value_type;
	typedef T* pointer;
	typedef T& reference;
	
	iterator_on_one_element( T val ) : _val(val) {}
	
	template <typename size_type>
	iterator_on_one_element<T>& operator+(size_type i) { return *this; }
	iterator_on_one_element<T>& operator++() { return *this; }
	
	T operator*() { return _val; }

	///////////	for fast iterator /////////////////////////////////////////
	void push( int dim ) {}
	void pop( int dim )  {}
	void load_stride( int dim ) {}
	bool is_unit_stride( int dim ) { return true; }
	int  suggest_stride( int dim ) { return 1; }
	bool is_stride( int dim, int stride ) { return true; }
	bool can_collapse( int outer_dim, int inner_dim ) { return true; }
	void advance() {}
	void advance( int n ) {}
	T    operator[]( int i ) { return _val; }
	T    fast_read( int i ) { return _val; }
	///////////////////////////////////////////////////////////////////////
	
private:
	T _val;
};

/**
 *  @brief  a helper class to implement "array_binary_apply" using "non-bitwise copy && conventional loop"
 *  @tparam operation any operation types defined in namespace "binary_assign_ops"  
 *  @tparam in_iter  input iterator type (right hand side)
 *  @tparam out_iter output iterator type (left hand side)
 *  @tparam both_random_iter both iterator types are random_access_iterator (false in default)
 */
template <class operation, class in_iter, class out_iter, bool both_random_iter>
struct apply_helper
{
	static inline in_iter apply_impl( out_iter first, out_iter last, in_iter src )
	{
		for(; first != last; ++first, ++src)
			operation::apply( *first, *src );
		return src;
	}
};
	
/**
 *  @brief  a specialized helper class to implement "array_binary_apply" using "non-bitwise copy && loop unroalling"
 *  @tparam operation any operation types defined in namespace "binary_assign_ops"  
 *  @tparam in_iter  input iterator type (right hand side)
 *  @tparam out_iter output iterator type (left hand side)
 *
 *  If both_random_iter = true, which means both interators are random_access_iterator,
 *  we use loop unrolling technique to accelerate binary operator applying.
 */
template <class operation, class in_iter, class out_iter>
struct apply_helper<operation, in_iter, out_iter, true>
{
	typedef typename std::iterator_traits<out_iter>::difference_type iter_diff_type;
	static inline in_iter apply_impl( out_iter first, out_iter last, in_iter src )
	{
		iter_diff_type N = last - first;
		auto task = [ &first, &src ] ( int i ) 
			{  operation::apply( *(first+i), *(src+i) ); };
		unroll_loop<4>::loop(N, task);	
		return src + N;
	}
};
	
/**
 *  @brief  array_binary_apply: for binary operators such as =, +=, -=, *=, /=....
 *  @tparam operation any operation types defined in namespace "binary_assign_ops"  
 */
template <class operation>
struct array_binary_apply
{
	//! public interface
	template <class in_iter, class out_iter>
	static inline in_iter go( out_iter first, out_iter last, in_iter src )
	{
		static constexpr bool both_random_iter 
			= two_iter_check<in_iter, out_iter>::both_random;
		return apply_helper<operation, in_iter, out_iter, both_random_iter>
			::apply_impl( first, last, src );
	}
};

/**
 *  @brief  array_binary_apply: specialized version for "="
 */
template <>
struct array_binary_apply<binary_assign_ops::equal>
{
	//! public interface
	template <bool enable_bitwise_copy = false, class in_iter, class out_iter>
	static inline in_iter go( out_iter first, out_iter last, in_iter src )
	{
		//! deduce that both are random iterators 
		static constexpr bool both_random_iter 
			= two_iter_check<in_iter, out_iter>::both_random;

		//! deduce bitwise-copy supporting
		typedef typename std::iterator_traits<in_iter>::value_type  in_val_type;
		typedef typename std::iterator_traits<out_iter>::value_type out_val_type;
		static constexpr bool same_value_type = std::is_same<in_val_type, out_val_type>::value;		
		static constexpr bool type_support_bitwise_op = type_check<out_val_type>::support_bitwise_op;
		static constexpr bool bitwise_op = same_value_type && type_support_bitwise_op;
		
		typedef typename std::conditional<
			enable_bitwise_copy && bitwise_op, 
			bitwise_apply_impl<in_iter, out_iter>, 
			apply_helper<binary_assign_ops::equal, in_iter, out_iter, both_random_iter>
		>::type result_type;
	
		return result_type::apply_impl( first, last, src );
	}
	
private:
	//! bitwise copy using memmove
	template <class in_iter, class out_iter>
	struct bitwise_apply_impl
	{		
		typedef typename std::iterator_traits<out_iter>::difference_type iter_diff_type;		
		typedef typename std::iterator_traits<out_iter>::value_type  T;
		static inline in_iter apply_impl( out_iter first, out_iter last, in_iter src )
		{
			using binary_assign_ops::equal;
			iter_diff_type N = last - first;
			memmove( first, src, sizeof(T) * N );
			return src + N;
		}		
	};
	
};

/**
 *  @brief  array_binary_logical_apply: for binary logical operations such as ==, >, <, !=...
 */
template <class operation, class bin_op_for_sum = binary_assign_ops::bitand_equal>
struct array_binary_logical_apply
{
	template <class iter_L, class iter_R>
	static bool go( iter_L first_L, iter_L last_L, iter_R first_R )
	{
		static constexpr bool both_random_iter 
			= two_iter_check<iter_L, iter_R>::both_random;

		typedef typename std::conditional<
			both_random_iter, std::true_type, std::false_type
		>::type result_type;
			
		return apply_impl( first_L, last_L, first_R, result_type() );
	}

private:
	//! a standard implementation if not both iterators are random_access_iterator
	template <class iter_L, class iter_R>
	static bool apply_impl( iter_L first_L, iter_L last_L, iter_R first_R, std::false_type )
	{
		bool res = true;
		for(; first_L != last_L; ++first_L, ++first_R)
		{
			bool _res = operation::apply( *first_L, *first_R );
			bin_op_for_sum::apply( res, _res );
		}
		return res;	
	}
	
	//! if both iterators are random_access_iterators, using loop unrolling
	template <class iter_L, class iter_R>
	static bool apply_impl( iter_L first_L, iter_L last_L, iter_R first_R, std::true_type )
	{
		typedef typename std::iterator_traits<iter_L>::difference_type iter_diff_type;
		iter_diff_type N = last_L - first_L;
		bool res = true;
		auto task = [ &first_L, &first_R, &res ] ( int i ) 
		{  
			bool _res = operation::apply( first_L, first_R );
			bin_op_for_sum::apply( res, _res );
		};
		unroll_loop<4>::loop(N, task);	
		return res;
	}
};

#endif //!< ARRAY_COPY_H